package handConsistentHash;

import java.util.Collection;
import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * @author 03010570
 * @date 2020/07/08
 * describe:
 * 属性：
 * 1、有序虚拟节点    TreeMap
 * 2、哈希算法  HashFunction
 * <p>
 * 方法：
 * 1、构造函数  新增多个物理节点
 * 2、新增物理节点
 * 3、删除物理节点
 * 4、根据 ip 查找虚拟节点对应的物理节点
 * 5、获取 已存在的物理节点 的下标
 * <p>
 * 静态内部类：
 * 1、 哈希算法 HashFunction
 */
public class ConsistentHashAlgorithm {

    private SortedMap<Long, VNode> ring = new TreeMap<>();
    private HashFunction hashFunction = new HashFunction();

    public ConsistentHashAlgorithm(Collection<PNode> pNodes, Integer vNodeCount) {
        for (PNode pNode : pNodes) {
            addNode(pNode, vNodeCount);
        }
    }

    /**
     * 新增物理节点
     *
     * @param pNode
     */
    public void addNode(PNode pNode, Integer vNodeCount) {
        Integer existingReplicas = getReplicas(pNode.toString());
        for (int i = 0; i < vNodeCount; i++) {
            VNode vNode = new VNode(pNode, i + existingReplicas);
            ring.put(hashFunction.hash(vNode.toString()), vNode);
        }
    }

    /**
     * 删除物理节点
     * 1、根据 物理节点获取到 ring 当中的所有 虚拟节点，并使用 迭代器进行删除
     * Tip : 要 根据 key 来删除 value ,所以要先计算出 hash key
     *
     * @param pNode
     */
    public void removeNode(PNode pNode) {
        Iterator<Long> iterator = ring.keySet().iterator();
        while (iterator.hasNext()) {
            Long next = iterator.next();
            VNode vNode = ring.get(next);
            if (vNode.matches(pNode.toString())) {
                iterator.remove();
            }
        }
    }

    /**
     * 根据 key 获取物理节点
     *
     * @param key
     * @return
     */
    public PNode getNode(String key) {
        Long hashKey = hashFunction.hash(key);
        SortedMap<Long, VNode> sortedMap = ring.tailMap(hashKey);
        hashKey = sortedMap != null && sortedMap.size() > 0 ? sortedMap.firstKey() : ring.firstKey();
        return ring.get(hashKey).getpNode();
    }

    /**
     * 根据 name 获取物理节点的下标，
     * 1、如果当前 ring 环中不存在该物理节点，返回 0
     * 2、如果当前 ring 环中存在该物理节点，对物理节点的虚拟节点进行计数，返回计数
     *
     * @param name
     * @return
     */
    public int getReplicas(String name) {
        int index = 0;
        for (VNode vNode : ring.values()) {
            if (vNode.matches(name)) {
                index++;
            }
        }
        return index;
    }
}
